package io.gitee.hechenl.easyopc.opcua;

import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedSubscription;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.*;
import org.eclipse.milo.opcua.stack.core.types.structured.DataChangeFilter;
import org.springframework.util.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

@Slf4j
public class EasyOpcUaUtil {

    public static OpcUaClient createClient(String serverIp, String appName, String appUri) throws Exception {
        return OpcUaClient.create(
                serverIp,
                endpoints ->
                        endpoints.stream()
                                .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                                .findFirst(),
                configBuilder ->
                        configBuilder
                                .setApplicationName(LocalizedText.english(appName))
                                .setApplicationUri(appUri)
                                .setRequestTimeout(uint(5000))
                                .build()
        );
    }

    public static ManagedSubscription subscript(OpcUaClient client, List<NodeId> nodeIdList, EasyOpcUaConsumer consumer) throws Exception {
        String clientName = client.getConfig().getApplicationName().getText();
        ManagedSubscription subscription = ManagedSubscription.create(client);
        subscription.setDefaultSamplingInterval(0.0);
        subscription.setDefaultMonitoringMode(MonitoringMode.Reporting);
        subscription.setDefaultDataFilter(new DataChangeFilter(
                DataChangeTrigger.StatusValue,
                UInteger.valueOf(DeadbandType.None.getValue()),
                0.0));
        subscription.setDefaultQueueSize(UInteger.valueOf(1));
        subscription.setDefaultDiscardOldest(true);

        subscription.addDataChangeListener((items, values) -> {
            for (int i = 0; i < items.size(); i++) {
                try {
                    log.info("Opc ua subscription value received: client={}, item={}, value={}, loop={}",
                            clientName, items.get(i).getNodeId(), values.get(i).getValue(), i);
                    consumer.consume(clientName, items.get(i).getNodeId(), values.get(i).getValue());
                } catch (Exception e) {
                    log.error("Opc ua data handler error", e);
                }
            }
        });
        List<ManagedDataItem> dataItems = subscription.createDataItems(nodeIdList);
        if (CollectionUtils.isEmpty(dataItems)) {
            throw new RuntimeException("Opc ua subscription createDataItem null");
        }
        for (ManagedDataItem dataItem : dataItems) {
            if (!dataItem.getStatusCode().isGood()) {
                log.error("Opc ua subscription createDataItem not good, clientName={}, nodeId={}, statusCode={}",
                        clientName, dataItem.getNodeId(), dataItem.getStatusCode());
            }
        }
        return subscription;
    }

    public static Object read(OpcUaClient client, NodeId nodeId) {
        try {
            UaVariableNode node = client.getAddressSpace().getVariableNode(nodeId);
            DataValue value = node.readValue();
            return value.getValue().getValue();
        } catch (Exception e) {
            log.error("Opc ua read error, appName:{}, nodeId:{}",
                    client.getConfig().getApplicationName(), nodeId.toString(), e);
        }
        return null;
    }

    public static List<Object> readList(OpcUaClient client, List<NodeId> nodeIdList) {
        try {
            client.connect().get();
            CompletableFuture<List<DataValue>> future = client.readValues(0.0, TimestampsToReturn.Both, nodeIdList);
            List<DataValue> dataValues = future.get();
            if (!CollectionUtils.isEmpty(dataValues)) {
                return dataValues.stream().map(v -> v.getValue().getValue()).collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("Opc ua readList error, appName:{}, nodeIdList:{}",
                    client.getConfig().getApplicationName(), nodeIdList, e);
        }
        return Collections.emptyList();
    }

    public static List<NodeId> convertNodeId(Integer namespace, List<String> identifiers) {
        return identifiers.stream().map(
                value -> new NodeId(namespace, value)).collect(Collectors.toList());
    }

}